package org.gwu.seuss.service;

import java.util.List;

import javax.crypto.SecretKey;

import org.apache.commons.lang3.StringUtils;
import org.gwu.seuss.communication.crypto.CryptoUtil;
import org.gwu.seuss.communication.crypto.CryptoVal;
import org.gwu.seuss.communication.exception.FileException;
import org.gwu.seuss.communication.exception.FileIntegrityException;
import org.gwu.seuss.communication.exception.SecretKeyException;
import org.gwu.seuss.communication.exception.UserException;
import org.gwu.seuss.communication.request.ClientRequest;
import org.gwu.seuss.communication.util.FileUtils;
import org.gwu.seuss.domain.File;
import org.gwu.seuss.domain.User;

public class FileServiceImpl extends ServiceBase implements FileService {

	private FileServiceImpl(){
		
	}

	@Override
	public int insertFile(File file) {
		return super.filesMapper.insertFile(file);
	}

	@Override
	public List<File> selectAllFileByOwner(int ownerId) {
		return super.filesMapper.selectAllFileByOwner(ownerId);
	}

	@Override
	public File selectFile(int ownerId, String filename) {
		return super.filesMapper.selectFile(ownerId, filename);
	}

	@Override
	public int deleteFile(String owner, String filename) throws Exception {
		User user = super.usersMapper.selectUserByEmail(owner);
		int result = super.filesMapper.deleteFile(user.getUserId(), filename);
		
		if(result > 0) {
			FileUtils.deleteFile(filename, user.getDirectory());
		} else {
			throw new Exception("Failed to delete " + filename);
		}
		
		return result;
	}

	@Override
	public byte[] downloadFile(ClientRequest req) throws Exception {
		String username = req.getUsername();
		String filename = req.getFileName();
		
		User owner = usersMapper.selectUserByEmail(username);
		
		File file = selectFile(owner.getUserId(), filename);
		
		if(file == null) {
			log.error("File does not exist with id " + req.getFileId());
			throw new FileException("File does not exist.");
		}

		log.info("User " +  username + " requested to donwload file " + filename);
		
		String location = owner.getDirectory();
		
		// Find file on local disk
		byte[] encFile = FileUtils.readFile(filename, location);

		User user = usersMapper.selectUserByEmail(username);
		if(user == null) {
			throw new UserException("User does not exist");
		}

		// Obtain the user secret key to decrypt file
		SecretKey loadedKey = CryptoUtil.getSecretKeyFromKeyStore(
				owner.getDirectory(), req.getPassword());

		if (loadedKey == null) {
			throw new SecretKeyException("Failed to load secret key for user "
					+ req.getUsername());
		}

		// Obtain hash from encrypted file
		String encHash = CryptoUtil.generateHmac(loadedKey, encFile);

		// verify hash
		if (!StringUtils.equals(file.getMac(), encHash)) {
			// Hashes do not match
			log.error("The hash check failed");
			throw new FileIntegrityException("File hash does not match");
		} else {
			// Decrypt
			return CryptoUtil.decrypt(file.getIv(), loadedKey, encFile);
		}
	}

	@Override
	public int uploadFile(ClientRequest req) throws Exception {
		User user = usersMapper.selectUserByEmail(req.getUsername());
		if(user == null) {
			throw new UserException("User does not exist");
		}
		
		SecretKey loadedKey = CryptoUtil.getSecretKeyFromKeyStore(user.getDirectory(), req.getPassword());
				
		if(loadedKey == null) {
			throw new SecretKeyException("Failed to load secret key for user " + req.getUsername());
		}
		
		// Encrypt the file
		CryptoVal val = CryptoUtil.encrypt(loadedKey, req.getFileBytes());

		// Obtain the HMAC
		String hash = CryptoUtil.generateHmac(loadedKey, val.getEncFile());

		// Create directory for user
		String location = user.getDirectory();
		FileUtils.createDirectory(location);

		// Save file to local disk
		FileUtils.writeFile(req.getFileName(), location, val.getEncFile());
		log.info("Uploaded encrypted file " + req.getFileName());
		
		// Save the file record to DB
		File file = new File();
		file.setOwnerId(user.getUserId());
		file.setFilename(req.getFileName());
		file.setIv(val.getIv());
		file.setMac(hash);
		file.setPath("");
		file.setDeleted(false);
		
		return insertFile(file);
	}
}